using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Runtime.Serialization;

namespace Rogue
{
    public class EntityCollector
    {

        private SortedDictionary<Point, List<Entity> > m_Entities;
        public SortedDictionary<Point, List<Entity>> Entities
            { get { return m_Entities; } }

        class PointComparer : Comparer<Point>
        {
            public override int Compare(Point a, Point b)
            {
                if (a.X < b.X)
                {
                    return -1;
                }
                else if (a.X > b.X)
                {
                    return 1;
                }

                if (a.Y < b.Y)
                {
                    return -1;
                }
                else if (a.Y > b.Y)
                {
                    return 1;
                }

                return 0;
            }
        };


        public EntityCollector()
        {
            m_Entities = new SortedDictionary<Point, List<Entity> >( new PointComparer() );
        }

        public void Clear()
        {
            Entities.Clear();
        }

        // fill ourselves (eg on map load) to reflect a new list of entities
        public void Repopulate(GameMap a_Map)
        {
            Clear();

            foreach(KeyValuePair<string, Entity> kvp in a_Map.m_EntityTable)
            {
                Add(kvp.Value);
            }
        }

        #region moving entities around
        public void Add(Entity a_Ent)
        {
            Add(a_Ent, a_Ent.Position);
        }

        public void Add(Entity a_Ent, Point a_To)
        {
            List<Entity> thisCell;
            if (!Entities.TryGetValue(a_To, out thisCell))
            {
                thisCell = new List<Entity>();
                Entities[a_To] = thisCell;
            }

            thisCell.Add(a_Ent);
        }

        public void Remove(Entity a_Ent)
        {
            Remove(a_Ent, a_Ent.Position);
        }

        public void Remove(Entity a_Ent, Point a_From)
        {
            if (Entities.ContainsKey(a_From))
            {
                List<Entity> thisCell = Entities[a_From];
                if (thisCell.Contains(a_Ent))
                {
                    thisCell.Remove(a_Ent);

                    if (thisCell.Count == 0)
                        Entities.Remove(a_From);
                }
            }
        }

        public void Update(Entity a_Ent, Point a_From, Point a_To)
        {
            Remove(a_Ent, a_From);
            Add(a_Ent, a_To);
        }
        #endregion

        #region world collection

        // Gives you all the entities at a certain cell
        public IEnumerable<T> GetAllAt<T>(int x, int y) where T: Entity
        {
            List<Entity> thisCell;
            if (!Entities.TryGetValue(new Point(x,y), out thisCell))
                yield break;

            foreach (Entity entity in thisCell)
            {
                T casted = entity as T;
                if (casted != null)
                    yield return casted;
            }
        }
        
        // entities of a certain type
        public IEnumerable<T> GetAllWithin<T>(Point a_P, float a_CellRadius) where T : Entity
        {
            int minX = (int)Math.Floor(a_P.X - a_CellRadius);
            int maxX = (int)Math.Ceiling(a_P.X + a_CellRadius);
            int minY = (int)Math.Floor(a_P.Y - a_CellRadius);
            int maxY = (int)Math.Ceiling(a_P.Y + a_CellRadius);

            float distSq = a_CellRadius * a_CellRadius;

            for (int x = minX; x <= maxX; ++x)
            {
                for (int y = minY; y <= maxY; ++y)
                {
                    float dx = a_P.X - x;
                    float dy = a_P.Y - y;
                    if (dx*dx + dy*dy > distSq) // skip this cell, it's too far away
                        continue;

                    foreach (T entity in GetAllAt<T>(x, y))
                        yield return entity;
                }
            }
        }

        private class DistFromMiddle<T> : IComparer<T> where T: Entity
        {
            Point m_Middle;

            public DistFromMiddle(Point a_P) : base()
            {
                m_Middle = a_P;
            }

            private int SquaredDist(Point a, Point b)
            {
                return (a.X - b.X) * (a.X - b.X) + 
                       (a.Y - b.Y) * (a.Y - b.Y);
            }

            // Returns < 0 if (a < b), which is when a is closer than b.
            public int Compare(T a, T b)
            {
                return SquaredDist(a.Position, m_Middle) - SquaredDist(b.Position, m_Middle);
            }
        };

        // in distance-sorted order (closest first)
        public List<T> GetAllWithinSorted<T>(Point a_P, float a_CellRadius) where T : Entity
        {
            List<T> returnMe = new List<T>();

            foreach (T entity in GetAllWithin<T>(a_P, a_CellRadius))
                returnMe.Add(entity);

            returnMe.Sort(new DistFromMiddle<T>(a_P));
            return returnMe;
        }

        #endregion

        // Make sure that entities update their positions nicely! Call this if you think you're having troubles.
        public void SanityCheck()
        {
            Dictionary<Entity, Point> reverseMap = new Dictionary<Entity, Point>();

            foreach(KeyValuePair<Point, List<Entity>> gridCell in Entities)
            {
                foreach (Entity entity in gridCell.Value)
                {
                    if (reverseMap.ContainsKey(entity))
                    {
                        // Something went bad with the Entity position updating for this entity.
                        throw new System.ApplicationException("Failed the EntityCollector sanitycheck, bad position updating.");
                    }

                    reverseMap.Add(entity, gridCell.Key);
                }
            }
        }
    }
}